package com.new4net.exe_stream.instruction.impl;

import com.new4net.common.UUIDUtil;
import com.new4net.exe_stream.instruction.Instruction;
import com.new4net.exe_stream.listener.InstructionListener;
import com.new4net.exe_stream.meta.ExecutionContext;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
public abstract class AbstractInstruction implements Instruction {

    protected String instructionId = UUIDUtil.getUUID();

    protected volatile Date exeStartTime;

    protected volatile Date exeEndTime;



    protected String instructionName;
    protected ExecutionContext executionContext;

    protected ExecutionContext.InstructionContext instructionContext ;

    @Override
    public ExecutionContext.InstructionContext getInstructionContext() {
        return instructionContext;
    }

    protected Object retVal;

    protected ExecuteResult executeResult = ExecuteResult.builder().build();

    protected List<InstructionListener> instructionListeners = new ArrayList<>();

    public AbstractInstruction(String instructionName) {
        this.instructionName = instructionName;
    }

    public AbstractInstruction addInstructionListener(InstructionListener instructionListener) {
        if(instructionListener!=null&&instructionListeners.contains(instructionListener)){
            instructionListeners.add(instructionListener);

        }
        return this;
    }

    public String getInstructionId() {
        return instructionId;

    }

    @Override
    public String getInstructionName() {
        return instructionName;
    }

    @Override
    public ExecuteResult getExecuteResult() {
        return executeResult;
    }
    @Override
    public Date getExeStartTime() {
        return exeStartTime;
    }

    @Override
    public Date getExeEndTime() {
        return exeEndTime;
    }
    @Override
    public void setExecutionContext(ExecutionContext executionContext) {
        this.executionContext = executionContext;
    }


    @Override
    public ExecutionContext getExecutionContext() {
        return executionContext;
    }

    @SneakyThrows
    @Override
    public void run() {

        try {
            prev();
            exe();
            post();
        } catch (Throwable e) {
            fault(e);

        }

    }

    protected abstract void exe();

    public void retry(){
        synchronized (this){
            try {
                retryPrev();
                exeRetry();
                post();
            } catch (Throwable e) {
                fault(e);

            }
        }
    }


    protected void exeRetry() {
        exe();
    }

    protected void post() {

        instructionContext.addParam("return", retVal);
        
        executionContext.addInstructionContext(instructionContext);
        executeResult.setStatus("SUCCESS");
        executeResult.setReturnVal(retVal);

        instructionListeners.forEach((listener -> {

            try {
                listener.post(this);

            } catch (Throwable e) {
                log.warn(listener.getName() + "监听器:后置监听异常:", e);
            }

        }));

        exeEndTime = new Date();
    }

    @SneakyThrows
    protected void fault(Throwable e) {

        instructionContext.addParam("return", e);
        
        executeResult.setStatus("FAIL");
        executeResult.setFailMessage(e.getMessage());
        executeResult.setError(e);
        instructionListeners.forEach((listener -> {
            try {
                listener.fault(e, this);

            } catch (Throwable e1) {
                log.warn(listener.getName() + "监听器:异常监听异常:", e1);
            }

        }));
        exeEndTime = new Date();

    }

    protected void prev() {
        exeEndTime = null;
        exeStartTime = new Date();
        if(instructionContext==null){
            instructionContext=executionContext.buildInstructionContext();
        }

        instructionListeners.forEach((listener -> {
            try {
                listener.prev(this);

            } catch (Throwable e) {
                log.warn(listener.getName() + "监听器:前置监听异常:", e);
            }

        }));
    }

    protected void step(String stepName) {
        instructionListeners.forEach((listener -> {
            try {
                listener.step(this, stepName);

            } catch (Throwable e) {
                log.warn(listener.getName() + "监听器:前置监听异常:", e);
            }

        }));
    }


    protected void retryPrev() {
        prev();
        executeResult.setStatus(null);
        executeResult.setFailMessage(null);
        executeResult.setReturnVal(null);

    }

}
